#include <iostream>
#include <QtOpenGL>
#include <GL/glu.h>

#include "sphere.h"
#include "glwidget.h"
#include "dynamicSolver.h"
#include "src/output.h"
#include "src/state.h"
#include "src/fabric.h"

#include "defines/functions.h"
#define THETA 18

class CalculatingThread: public QThread{
    public:
        CalculatingThread(GLWidget * widget)
        {
            this->widget = widget;
            stopped = false;
        }
        void run() {
            while(!stopped) {
                widget->calculate();
            }
        }
        void stop()
        {
            stopped = true;
            while(isRunning());
        }
        void restart(){
            stopped = false;
            start();
        }
    private:
        GLWidget * widget;
        bool stopped;
};

GLWidget::GLWidget(QWidget* _parent) : QGLWidget(_parent){
    wireframe = true;
    showAxis = true;
    showGrid = true;
    showFloor = false;
    showMast = false;
    showNormal = false;
    drawSmooth = true;
    drawPlayback = true;
    record = false;

    playback = NULL;
    thread = NULL;
    solver = NULL;
    playbackFabrics = NULL;

    zoom = 1.0;
	xRot = 0;//5584;
	yRot = 0;
	zRot = 0;//5136;
	xMov = 0;//-1;
	yMov = 0;//2.85;
	zMov = 0;//0.2;

	light0_pos[0] = 5.0;
	light0_pos[1] = -8.0;
	light0_pos[2] = 10.0;
	light0_pos[3] = 0.0;

	light0_ambient[0] = 0.1;
	light0_ambient[1] = 0.1;
	light0_ambient[2] = 0.1;
	light0_ambient[3] = 0.4;

	light0_diffuse[0] = 0.9;
	light0_diffuse[1] = 0.9;
	light0_diffuse[2] = 0.9;
	light0_diffuse[3] = 1.0;

	light0_specular[0] = 0.5;
	light0_specular[1] = 0.5;
	light0_specular[2] = 0.5;
	light0_specular[3] = 1.0;

	global_ambient[0] = 0.2;
	global_ambient[1] = 0.2;
	global_ambient[2] = 0.2;
	global_ambient[3] = 1.0;


//	t = 0.0;
	timer = new QTimer(this);
	connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));

	timer->start(16);

    if ( !texture.load( "texture/173.jpg" ) ) std::cout <<"Não carregou a textura" << std::endl;
}

GLWidget::~GLWidget(void) {
    if(thread) ((CalculatingThread*)thread)->stop();
    delete thread;
    delete timer;
    for(unsigned int i = 0; i < fabrics.size(); i++){
        if(fabrics.at(i)) delete fabrics.at(i);
    }
    fabrics.clear();
    for(unsigned int i = 0; i < objects.size(); i++){
        if(objects.at(i)) delete objects.at(i);
    }
    objects.clear();
    delete solver;
    if (playback) delete playback;
}

QSize GLWidget::minimumSizeHint(void) const {
	return QSize(50, 50);
}

QSize GLWidget::sizeHint(void) const {
	return QSize(400, 400);
}

void GLWidget::changeWireframe(bool _w) {
	wireframe = _w;
}

void GLWidget::changeShowAxis(bool _sa) {
	showAxis = _sa;
}

void GLWidget::changeShowGrid(bool _sg) {
	showGrid = _sg;
}

void GLWidget::changeShowFloor(bool _sf) {
	showFloor = _sf;
}

void GLWidget::changeShowMast(bool _sm) {
	showMast = _sm;
}

void GLWidget::changeShowNormal(bool _sn) {
	showNormal = _sn;
}

void GLWidget::changeDrawSmooth(bool _ds) {
	drawSmooth = _ds;
}

void GLWidget::changeDrawPlayback(bool _pb) {
	drawPlayback = _pb;
}

std::vector<Fabric *> GLWidget::loadFile(QFile *_file, unsigned int _method, double _deltaT, double _gravity, unsigned int _adjacency,
                                         bool _activeCollision, double _dampingFactor, double _distanceError,
                                         bool _activeAir, int _airType, double _airPeriod, double _airRepeat, double _airViscosity, Vector3D _airDirection)
{
    enterCS();

    for(unsigned int i = fabrics.size(); i > 0; i--){
        if(fabrics.at(i - 1)) delete fabrics.at(i - 1);
    }

    fabrics = input.parse(_file, _adjacency, &_deltaT, &_gravity);

	leaveCS();

	changeSolver(_method, _deltaT, _gravity, _activeCollision, _dampingFactor, _distanceError, _activeAir, _airType, _airPeriod, _airRepeat, _airViscosity, _airDirection);

    std::cout << "Tem " << fabrics.size() <<" tecidos  " <<std::endl;
	return fabrics;
}

std::vector<Fabric *> GLWidget::loadFile(QFile *_file)
{
    enterCS();

    if(playbackFabrics){
        for(unsigned int i = 0; i < playbackFabrics->size(); i++){
            if(playbackFabrics->at(i)) delete playbackFabrics->at(i);
        }
        playbackFabrics->clear();
    }

    playbackFabrics = input.parse(_file);

	leaveCS();

    std::cout << "Tem " << playbackFabrics->size() <<" tecidos no playback" <<std::endl;
	return *playbackFabrics;
}

Solver *GLWidget::changeSolver(unsigned int _method, double _deltaT, double _gravity,
                            bool _activeCollision, double _dampingFactor, double _distanceError,
                            bool _activeAir, int _airType, double _airPeriod, double _airRepeat, double _airViscosity, Vector3D _airDirection)
{
    enterCS();
    delete solver;
    solver = new DynamicSolver(&fabrics, &objects, _method, _deltaT, _gravity,
                               _activeCollision, _dampingFactor, _distanceError,
                               _activeAir, _airType, _airPeriod, _airRepeat, _airViscosity, _airDirection);
    connect((DynamicSolver *)solver, SIGNAL(recordSimulation(QString)), this, SLOT(recordSimulation(QString)));
	leaveCS();

	return solver;
}

void GLWidget::initializeGL(void) {
	glClearColor(0.42,0.42,0.42,1.0);
	glEnable(GL_DEPTH_TEST);
	glShadeModel(GL_FLAT); //cores normais
    glShadeModel(GL_SMOOTH); //smooth
	//glEnable(GL_CULL_FACE); //esconder faça de baixo

	glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);


    //luz
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);

    glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
    glLightfv(GL_LIGHT0, GL_AMBIENT, light0_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE,  light0_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light0_specular);

    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);

//    glEnable(GL_TEXTURE_2D);
}

void GLWidget::paintGL(void) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

    //translação
	glPushMatrix();
	glTranslated(xMov, yMov, zMov);

    //rotacao
	glRotated(xRot / 16.0, 1.0, 0.0, 0.0);
	glRotated(yRot / 16.0, 0.0, 1.0, 0.0);
	glRotated(zRot / 16.0, 0.0, 0.0, 1.0);

    if(drawPlayback)
    {
        if(!playbackFabrics->empty()){

            fabricTexture = bindTexture(texture);
            glBindTexture(GL_TEXTURE_2D, fabricTexture);
            //enterCS();
            for(unsigned int i = 0; i < playbackFabrics->size(); i++)
                playbackFabrics->at(i)->draw(wireframe, showNormal, drawSmooth);
            //leaveCS();
        }
        if(!objects.empty()){
            //enterCS();
            for(unsigned int i = 0; i < objects.size(); i++)
                objects.at(i)->draw(wireframe, showNormal);
            //leaveCS();
        }
    }
    else
    {
        if(!fabrics.empty()){
            //enterCS();

            fabricTexture = bindTexture(texture);
            glBindTexture(GL_TEXTURE_2D, fabricTexture);

            for(unsigned int i = 0; i < fabrics.size(); i++)
                fabrics.at(i)->draw(wireframe, showNormal, drawSmooth);

            //leaveCS();
        }
        if(!objects.empty()){
            //enterCS();
            for(unsigned int i = 0; i < objects.size(); i++)
                objects.at(i)->draw(wireframe, showNormal);
            //leaveCS();
        }
    }


    int square = 10;
    if(showGrid){
        glDisable(GL_LIGHT0);
        glDisable(GL_LIGHTING);
        //eixos

        glColor4f(0.2, 0.2, 0.2, 0.7);
        glBegin(GL_LINES);
        for(double i = -square; i <= square; i = i + 0.5){
            glVertex3f( i, square, 0);
            glVertex3f( i, -square, 0);

            glVertex3f( square, i, 0);
            glVertex3f( -square, i, 0);
        }
        glEnd();

        glEnable(GL_LIGHT0);
        glEnable(GL_LIGHTING);

    }

    double depth = -2.5;
    if(showFloor){
        glColor4f(0.88, 0.88, 0.88, 1);
        glNormal3f(0.0, 0.0, 1.0);
        glBegin(GL_QUADS);
        glVertex3f( square, square, depth);
        glVertex3f( square, -square, depth);

        glVertex3f( -square, -square, depth);
        glVertex3f( -square, square, depth);
        glEnd();
    }

    if(showMast){
        glColor4f(0.0, 0.0, 0.0, 1);

        glPushMatrix();

        glTranslated(-1.05, 0.0, depth);
        glScaled(0.05, 0.05, 3);
        double radius = 1;
        double height = 1;


        //desenhando a base em cima
        glBegin(GL_POLYGON);
        glNormal3f(0, 0, 1);

        for(double i=0; i<=360.0; i = i+THETA){
            double theta = toRad(i);
            double x = radius*cos(theta);
            double y = radius*sin(theta);

            glVertex3f(x, y, height);
        }
        glEnd();


        glBegin(GL_QUAD_STRIP);
        Vector3D p1, p2, p3, normal;
        for(double i=0; i<=360.0; i = i+THETA){
            double theta = toRad(i);

            double x = radius*cos(theta);
            double y = radius*sin(theta);
            p1 = Vector3D(x, y, height);
            p2 = Vector3D(x, y, 0);

            theta = toRad(i + THETA);
            x = radius*cos(theta);
            y = radius*sin(theta);
            p3 = Vector3D(x, y, height);

            normal = ((Vector3D(0, 0, -1)).crossProduct(p3-p1)).normalize();

            glNormal3f(normal.x, normal.y, normal.z);

            glVertex3f(p1.x, p1.y, p1.z);
            glVertex3f(p2.x, p2.y, p2.z);
        }
        glEnd();

        //desenhando a base em baixo
        glBegin(GL_POLYGON);
        glNormal3f(0, 0, -1);

        for(double i=0; i<=360.0; i = i+THETA){
            double theta = toRad(-i);
            double x = radius*cos(theta);
            double y = radius*sin(theta);

            glVertex3f(x, y, 0);
        }
        glEnd();


        glPopMatrix();
    }


    if(showAxis){
        glDisable(GL_LIGHT0);
        glDisable(GL_LIGHTING);
        //eixos
        glLineWidth(2);

        glBegin(GL_LINES);
            glColor3f(1.0, 0.0, 0.0);
            //eixo X
            glVertex3f( 0, 0, 0);
            glVertex3f( 0.1, 0, 0);

            glColor3f(0.0, 1.0, 0.0);
            //eixo y
            glVertex3f( 0, 0, 0);
            glVertex3f( 0, 0.1, 0);

            glColor3f(0.0, 0.0, 1.0);
            //eixo z
            glVertex3f( 0, 0, 0);
            glVertex3f( 0, 0, 0.1);
        glEnd();
        glLineWidth(1);

        glEnable(GL_LIGHT0);
        glEnable(GL_LIGHTING);
    }
	glPopMatrix();
}

void GLWidget::resizeGL(int width, int height) {

	glViewport(0, 0, width, height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	double f = 0.3 * zoom;
    if (width <= height)
		glFrustum(-f,     f,    -f*((double)height)/width,    f*((double)height)/width,     1.0, 50.0);
	else
		glFrustum(-f*((double)width)/height,  f*((double)width)/height,     -f,     f,    1.0, 50.0);

	gluLookAt(0.0, -10.0, 3.0,   0.0, 0.0, 0.0,  0.0, 0.0, 1.0);
	glMatrixMode(GL_MODELVIEW);
}

void GLWidget::wheelEvent(QWheelEvent *_event){
    if(_event->delta() > 0){
        if (zoom > 1){
            if(_event->modifiers() & Qt::ShiftModifier)
                zoom -= 2.0;
            else
                zoom -= 0.1;
        }else if(zoom > 0.1){
            if(_event->modifiers() & Qt::ShiftModifier)
                zoom -= 0.5;
            else
                zoom -= 0.01;
        }
    }
    else{
        if (zoom < 100.0){
            if(_event->modifiers() & Qt::ShiftModifier)
                zoom += 2.0;
            else
                zoom += 0.1;
        }
    }
    resizeGL(width(), height());
    updateGL();
}

void GLWidget::mouseMoveEvent(QMouseEvent *_event){
	int dx = _event->x() - lastPos.x();
	int dy = _event->y() - lastPos.y();
	double multiplier = 0.05;

	if (_event->buttons() & Qt::LeftButton) {
        if (_event->modifiers() & Qt::ShiftModifier) {
            if ( fabs(lastPos.x() - _event->x()) > 1 ){
                int dx = -1;
                if(lastPos.x() - _event->x() < 0.0) dx = 1;

                xMov += -multiplier * dx;
                updateGL();
            }
            if ( fabs(lastPos.y() - _event->y()) > 1 ){
                int dy = 1;
                if(lastPos.y() - _event->y() < 0.0) dy = -1;

                yMov += multiplier * dy;
                updateGL();
            }

		} else {
            setXRotation(xRot + 8 * dy);
            setYRotation(yRot + 8 * dx);
		}
	} else if (_event->buttons() & Qt::RightButton) {
        if (_event->modifiers() & Qt::ShiftModifier) {
            if ( fabs(lastPos.x() - _event->x()) > 3 ){
                int dx = -1;
                if(lastPos.x() - _event->x() < 0.0) dx = 1;

                xMov += -multiplier * dx;
                updateGL();
            }
            if ( fabs(lastPos.y() - _event->y()) > 3 ){
                int dz = 1;
                if(lastPos.y() - _event->y() < 0.0) dz = -1;

                zMov += multiplier * dz;
                updateGL();
            }

		} else {
            setXRotation(xRot + 8 * dy);
            setZRotation(zRot + 8 * dx);
		}
	}

    lastPos = _event->pos();
}

void GLWidget::mousePressEvent(QMouseEvent *event){
	lastPos = event->pos();
}

void GLWidget::normalizeAngle(int *angle){
	while (*angle < 0)
		*angle += 360 * 16;
	while (*angle > 360 * 16)
		*angle -= 360 * 16;
}

void GLWidget::setXRotation(int angle){
	normalizeAngle(&angle);
	if (angle != xRot) {
		xRot = angle;
		emit xRotationChanged(angle);
		updateGL();
	}
}

void GLWidget::setYRotation(int angle){
	normalizeAngle(&angle);
	if (angle != yRot) {
		yRot = angle;
		emit yRotationChanged(angle);
		updateGL();
	}
}

void GLWidget::setZRotation(int angle){
	normalizeAngle(&angle);
	if (angle != zRot) {
		zRot = angle;
		emit zRotationChanged(angle);
		updateGL();
	}
}

void GLWidget::calculate(){
    //QMutexLocker locker(&fabricMutex);
    if(!fabrics.empty()) {
        enterCS();
        solver->calculate(record);
        leaveCS();
    }
}

void GLWidget::enterCS() {
    fabricMutex.lock();
}

void GLWidget::leaveCS() {
    fabricMutex.unlock();
}

bool GLWidget::startPauseSimulation(bool _play){
//    std::cout <<"Começo do start/pause" << std::endl;

    if(!_play && !fabrics.empty()) std::cout <<"t = " << fabrics[0]->getState().getT() <<
    "\n\tmove(" << xMov << ", " <<  yMov << ", " << zMov << ")" <<
    "\n\trotate(" << xRot << ", " <<  yRot << ", " << zRot << ")" <<
    "\n\tzoom" << zoom <<std::endl;

    if(drawPlayback)
    {
        //preparar os slots e signals para funcionar de tempo e tempo
        if(_play)
        {
            input.startPlayback();

            return true;
        }
        else input.pausePlayback();
    }
    else
    {
        if(!thread && _play && solver){
            thread = new CalculatingThread(this);
                thread->start();

//            std::cout <<"startou pela primeira vez" << std::endl;
        }
        if(_play) {
            if(thread){
                ((CalculatingThread *) thread)->restart();
//                std::cout <<"restartou" << std::endl;
                return true;
            }
        }
        else{
            if(thread) ((CalculatingThread *) thread)->stop();
//            std::cout <<"pausou" << std::endl;
        }
    }
	return false;
}

void GLWidget::stopSimulation(){
    if(drawPlayback)
    {
        input.stopPlayback();
    }
    else
    {
        if(thread) ((CalculatingThread *) thread)->stop();
        if(solver) solver->restart();
    }
}

void GLWidget::restartSimulation(){
//    if(drawPlayback) loadFile();
    stopSimulation();
    startPauseSimulation(true);
}

void GLWidget::recordSimulation(bool _b, QFile *_file){
    record = _b;
    if(_b){
        if(playback) delete playback;
        playback = _file;


        Output output;
        QString str = "";
        str.append( QString("# tecido ( c ID numeroParticulas )\n") );
        for(unsigned int i = 0; i < fabrics.size(); i++){
            str.append( QString("c ") );
            str.append( QString::number(fabrics.at(i)->getID()) );
            str.append( QString(" ") );
            str.append( QString::number(fabrics.at(i)->getParticlesSize()) );
            str.append( QString("\n") );

            if(fabrics.at(i)->hasTextureCoord()) str.append( fabrics.at(i)->getUV() );
            str.append( fabrics.at(i)->getSpringsFaces() );


            output.parseRecordSimulation(playback, str);
        }

        str = "# particulas no tempo ( t tecidoID tempo p1X p1Y p1Z p2X ...  )\n";
        for(unsigned int i = 0; i < fabrics.size(); i++){
            str.append( QString("t ") );
            str.append( QString::number(i) );
            str.append( fabrics.at(i)->getInitialState() );
            output.parseRecordSimulation(playback, str);
        }
    }
}

void GLWidget::recordSimulation(QString _s){
    if(record){
        Output output;
        output.parseRecordSimulation(playback, _s);
    }
}

void GLWidget::deleteObject(Object *_o){
    int size = objects.size();
    for (int i = size; i > 0; i--){
        if ( objects.at(i - 1) == _o ){
            objects.erase(objects.begin() + i - 1);
        }
    }
    delete _o;
}

void GLWidget::deleteFabric(Fabric *_f){
    stopSimulation();

    if(drawPlayback)
    {
        int size = playbackFabrics->size();
        for (int i = size; i > 0; i--){
            if ( playbackFabrics->at(i - 1) == _f ){
                playbackFabrics->erase(playbackFabrics->begin() + i - 1);
            }
        }
        delete _f;
    }
    else
    {
        int size = fabrics.size();
        for (int i = size; i > 0; i--){
            if ( fabrics.at(i - 1) == _f ){
                fabrics.erase(fabrics.begin() + i - 1);
            }
        }
        delete _f;
    }
}
